Saltar al contenido principal

Agencia de Exploradores

Esta actividad NO es un simulacro de examen. Su extensión supera lo que se solicitará en el examen de la convocatoria ordinaria. Además, en este modelo se facilita información para la autocorrección de la prueba (rubrica detallada).

Se pretende que sirva de preparación para la prueba real ya que el formato de enunciado será similar exceptuando los detalles de la rúbrica y los elementos válidos e inválidos esperados en los sets de datos.

Contexto​

La agencia Rutas Salvajes organiza expediciones guiadas por distintos entornos naturales. Para mejorar su gestión interna, necesita una aplicación en Python que permita administrar exploradores, expediciones y reservas.

Se deberá implementar el sistema siguiendo los principios de la Programación Orientada a Objetos.

No se permite acceder directamente a los atributos de las clases desde fuera de ellas. Se deberán utilizar propiedades, getters, setters o métodos específicos cuando sea necesario.


Archivos entregados​

Se proporciona la siguiente estructura de proyecto. 🔗 Descargar Recursos

examen_final_python/
│
├── main.py
├── explorador.py
├── expediciones.py
├── agencia.py
├── carga_datos.py
│
├── data/
│ ├── exploradores.csv
│ ├── expediciones.csv
│ └── reservas.json

El archivo main.py ya estará implementado y no se deberá modificar.

El alumnado deberá completar los archivos:

explorador.py
expediciones.py
agencia.py
carga_datos.py

Parte 1: Gestión de exploradores​

Modifica el fichero explorador.py para crear la clase Explorador.

Clase Explorador​

Atributos de instancia​

id_explorador
nombre
nivel
presupuesto
  • id_explorador: cadena de texto.
  • nombre: cadena de texto.
  • nivel: cadena de texto. Puede ser "principiante", "intermedio" o "experto".
  • presupuesto: número real.

Restricciones​

  1. El presupuesto no puede ser negativo.
  2. El nivel solo podrá tener uno de estos valores:
principiante
intermedio
experto
  1. Si se intenta asignar un presupuesto negativo, se lanzará un ValueError.
  2. Si se intenta asignar un nivel no válido, se lanzará un ValueError.

Método mágico​

Implementa el método mágico necesario para que, al imprimir un explorador, se muestre con el siguiente formato:

<nombre> (<nivel>) - Presupuesto: <presupuesto> €

Ejemplo:

Laura (intermedio) - Presupuesto: 450.0 €

Parte 2: Expediciones​

Modifica el fichero expediciones.py.

Se deberá crear una clase base Expedicion y dos clases hijas:

ExpedicionMontana
ExpedicionSelva

Clase Expedicion​

Atributo compartido​

Todas las expediciones tendrán un atributo compartido llamado:

empresa

Su valor por defecto será:

Rutas Salvajes

Atributos de instancia​

id_expedicion
nombre
dificultad
duracion_dias
precio_base
plazas_totales
plazas_ocupadas
  • id_expedicion: cadena de texto.
  • nombre: cadena de texto.
  • dificultad: "baja", "media" o "alta".
  • duracion_dias: número entero.
  • precio_base: número real.
  • plazas_totales: número entero.
  • plazas_ocupadas: número entero. Por defecto será 0.

Restricciones​

  1. precio_base no puede ser negativo.
  2. duracion_dias debe ser mayor que 0.
  3. plazas_totales debe ser mayor que 0.
  4. plazas_ocupadas no puede ser negativa.
  5. plazas_ocupadas no puede ser mayor que plazas_totales.
  6. dificultad solo podrá ser "baja", "media" o "alta".

Si alguna de estas restricciones no se cumple, se lanzará un ValueError.

Métodos de la clase Expedicion​

  • calcular_precio_final()

Debe devolver el precio final de la expedición.

El cálculo será:

precio_final = precio_base * multiplicador_dificultad

Los multiplicadores serán:

baja → 1
media → 1.25
alta → 1.5
  • hay_plazas_disponibles()

Debe devolver True si quedan plazas disponibles y False en caso contrario.

  • reservar_plaza()

Debe aumentar en 1 el número de plazas ocupadas.

Si no hay plazas disponibles, deberá lanzar un ValueError.

  • cancelar_plaza()

Debe reducir en 1 el número de plazas ocupadas.

Si no hay plazas ocupadas, deberá lanzar un ValueError.

  • realizar_expedicion()

Método polimórfico.

En la clase base debe lanzar una excepción NotImplementedError.

Clases hijas​

Clase ExpedicionMontana​

Hereda de Expedicion.

Atributo específico

altura_maxima

Será un número entero que representa la altura máxima de la ruta en metros.

Sobrescritura de realizar_expedicion()

Debe devolver el siguiente mensaje:

Ascendiendo hasta <altura_maxima> metros en la expedición <nombre>.

Modificación del precio

Las expediciones de montaña tendrán un suplemento fijo de 40 € sobre el precio final.

Por tanto, en esta clase deberá sobrescribirse el método calcular_precio_final().

Clase ExpedicionSelva​

Hereda de Expedicion.

Atributo específico

riesgo_fauna

Será una cadena de texto que podrá tener los valores:

bajo
medio
alto

Si el valor no es válido, se lanzará un ValueError.

Sobrescritura de realizar_expedicion()

Debe devolver el siguiente mensaje:

Explorando la selva con riesgo de fauna <riesgo_fauna> en la expedición <nombre>.

Modificación del precio

Las expediciones de selva con riesgo de fauna "alto" tendrán un suplemento de 60 € sobre el precio final.

Las expediciones de selva con riesgo "bajo" o "medio" no tendrán suplemento.


Parte 3: Objeto Agencia​

Crea la clase Agencia en el fichero agencia.py.

La agencia deberá gestionar exploradores, expediciones y reservas.

Atributos​

exploradores
expediciones
reservas
  • exploradores: diccionario donde la clave será el ID del explorador y el valor será un objeto Explorador.
  • expediciones: diccionario donde la clave será el ID de la expedición y el valor será un objeto ExpedicionMontana o ExpedicionSelva.
  • reservas: lista de diccionarios con la información de las reservas realizadas.

No se podrán modificar directamente estos atributos desde fuera de la clase.


Parte 4: Métodos de la agencia​

La clase Agencia deberá contener los siguientes métodos.

agregar_explorador(explorador)​

Recibe un objeto Explorador.

Debe añadirlo al diccionario de exploradores.

Si ya existe un explorador con el mismo ID, se lanzará un ValueError.

agregar_expedicion(expedicion)​

Recibe un objeto de tipo Expedicion.

Debe añadirlo al diccionario de expediciones.

Si ya existe una expedición con el mismo ID, se lanzará un ValueError.

buscar_explorador(id_explorador)​

Recibe el ID de un explorador.

Debe devolver el objeto Explorador correspondiente.

Si no existe, se lanzará un ValueError.

buscar_expedicion(id_expedicion)​

Recibe el ID de una expedición.

Debe devolver el objeto Expedicion correspondiente.

Si no existe, se lanzará un ValueError.

realizar_reserva(id_explorador, id_expedicion)​

Recibe el ID de un explorador y el ID de una expedición.

Debe realizar una reserva si se cumplen todas estas condiciones:

  1. El explorador existe.
  2. La expedición existe.
  3. La expedición tiene plazas disponibles.
  4. El explorador tiene presupuesto suficiente para pagar la expedición.

Si se realiza la reserva:

  • Se ocupará una plaza en la expedición.
  • Se restará el precio final de la expedición al presupuesto del explorador.
  • Se añadirá una reserva a la lista de reservas.

La reserva se guardará como un diccionario con esta estructura:

{
"id_explorador": "EX001",
"id_expedicion": "EP001",
"precio_pagado": 275.0
}

Si no se puede realizar la reserva, se lanzará un ValueError.

cancelar_reserva(id_explorador, id_expedicion)​

Debe cancelar una reserva existente.

Si existe la reserva:

  • Se libera una plaza de la expedición.
  • Se devuelve el importe pagado al presupuesto del explorador.
  • Se elimina la reserva de la lista.

Si la reserva no existe, se lanzará un ValueError.

expedicion_mas_cara()​

Debe devolver el objeto expedición con mayor precio final.

expediciones_disponibles()​

Debe devolver una lista con las expediciones que tienen plazas disponibles.

recaudacion_total()​

Debe devolver la suma de todos los precios pagados en las reservas realizadas.

tipo_mas_rentable()​

Debe devolver qué tipo de expedición ha generado mayor recaudación.

Devolverá uno de estos textos:

montana
selva
empate
sin reservas

Parte 5: Carga de datos​

Modifica el fichero carga_datos.py.

Se deberán implementar las siguientes funciones:

cargar_exploradores(agencia, path_fichero)​

Recibe:

agencia
path_fichero

Debe leer el archivo exploradores.csv, crear objetos Explorador y añadirlos a la agencia.

Debe devolver el objeto agencia modificado.

Las filas incorrectas se ignorarán.

cargar_expediciones(agencia, path_fichero)​

Recibe:

agencia
path_fichero

Debe leer el archivo expediciones.csv, crear objetos ExpedicionMontana o ExpedicionSelva según corresponda y añadirlos a la agencia.

Debe devolver el objeto agencia modificado.

Las filas incorrectas se ignorarán.

cargar_reservas(agencia, path_fichero)​

Recibe:

agencia
path_fichero

Debe leer el archivo reservas.json y realizar las reservas iniciales usando el método realizar_reserva().

Debe devolver el objeto agencia modificado.

Las reservas incorrectas se ignorarán.


Parte 6: Guardado de datos​

En el fichero carga_datos.py, implementa también la función:

guardar_reservas(agencia, path_fichero)​

Recibe:

agencia
path_fichero

Debe guardar en un archivo JSON todas las reservas actuales de la agencia.

La estructura del archivo generado será:

[
{
"id_explorador": "EX001",
"id_expedicion": "EP001",
"precio_pagado": 275.0
}
]

Parte 7: Métodos mágicos​

Implementa el método mágico necesario para que, al imprimir una expedición, se muestre con el siguiente formato:

<nombre> | <dificultad> | <duracion_dias> días | <precio_final> €

Ejemplo:

Ruta del Dragón | alta | 4 días | 490.0 €

Datasets​

Dataset 1: exploradores.csv​

id_explorador,nombre,nivel,presupuesto
EX001,Laura,intermedio,500
EX002,Marcos,principiante,180
EX003,Sara,experto,900
EX004,Diego,intermedio,300
EX005,Nerea,experto,700
EX006,Pablo,principiante,-50
EX007,Irene,avanzado,450
EX008,,intermedio,250
EX009,Hugo,principiante,220
EX010,Ana,experto,notiene

Filas válidas esperadas​

EX001
EX002
EX003
EX004
EX005
EX009

Filas que deben ignorarse​

EX006 → presupuesto negativo
EX007 → nivel no válido
EX008 → nombre vacío
EX010 → presupuesto no numérico

Dataset 2: expediciones.csv​

id_expedicion,tipo,nombre,dificultad,duracion_dias,precio_base,plazas_totales,extra
EP001,montana,Ruta del Dragón,alta,4,300,3,2400
EP002,selva,Sendero Esmeralda,media,3,180,2,medio
EP003,montana,Cumbre Blanca,media,2,150,2,1800
EP004,selva,Río Oculto,alta,5,260,1,alto
EP005,montana,Ladera Norte,baja,1,90,4,900
EP006,desierto,Dunas Rojas,media,3,200,2,calor
EP007,selva,Niebla Verde,baja,2,-120,3,bajo
EP008,montana,Pico Sombrío,alta,0,280,2,2100
EP009,selva,Pantano Azul,media,3,160,0,bajo
EP010,selva,Raíces Antiguas,alta,4,240,2,extremo

Interpretación del campo extra​

  • Si el tipo es montana, el campo extra representa altura_maxima.
  • Si el tipo es selva, el campo extra representa riesgo_fauna.

Filas válidas esperadas​

EP001
EP002
EP003
EP004
EP005

Filas que deben ignorarse​

EP006 → tipo no válido
EP007 → precio negativo
EP008 → duración igual a 0
EP009 → plazas totales igual a 0
EP010 → riesgo de fauna no válido

Dataset 3: reservas.json​

[
{
"id_explorador": "EX001",
"id_expedicion": "EP001"
},
{
"id_explorador": "EX002",
"id_expedicion": "EP002"
},
{
"id_explorador": "EX003",
"id_expedicion": "EP004"
},
{
"id_explorador": "EX004",
"id_expedicion": "EP004"
},
{
"id_explorador": "EX005",
"id_expedicion": "EP003"
},
{
"id_explorador": "EX009",
"id_expedicion": "EP005"
},
{
"id_explorador": "EX999",
"id_expedicion": "EP001"
},
{
"id_explorador": "EX001",
"id_expedicion": "EP999"
}
]

Reservas válidas esperadas​

EX001 → EP001
EX002 → EP002
EX003 → EP004
EX005 → EP003
EX009 → EP005

Reservas que deben ignorarse​

EX004 → EP004

Porque EP004 solo tiene 1 plaza y ya ha sido reservada por EX003.

EX999 → EP001

Porque el explorador no existe.

EX001 → EP999

Porque la expedición no existe.


Comprobaciones esperadas​

Estas comprobaciones pueden servir para el main.py o para la corrección.

Precios finales esperados​

EP001 → montaña, alta → 300 * 1.5 + 40 = 490.0
EP002 → selva, media, riesgo medio → 180 * 1.25 = 225.0
EP003 → montaña, media → 150 * 1.25 + 40 = 227.5
EP004 → selva, alta, riesgo alto → 260 * 1.5 + 60 = 450.0
EP005 → montaña, baja → 90 * 1 + 40 = 130.0

Presupuestos después de cargar reservas válidas​

EX001 → 500 - 490 = 10.0
EX002 → 180 - 225 → no se debería realizar, presupuesto insuficiente
EX003 → 900 - 450 = 450.0
EX005 → 700 - 227.5 = 472.5
EX009 → 220 - 130 = 90.0

Por tanto, realmente las reservas válidas finales deberían ser:

EX001 → EP001
EX003 → EP004
EX005 → EP003
EX009 → EP005

La reserva de EX002 → EP002 debe ignorarse porque el explorador no tiene presupuesto suficiente.


Resultado esperado de reservas cargadas​

Tras cargar los datos iniciales, deberían quedar estas reservas:

[
{
"id_explorador": "EX001",
"id_expedicion": "EP001",
"precio_pagado": 490.0
},
{
"id_explorador": "EX003",
"id_expedicion": "EP004",
"precio_pagado": 450.0
},
{
"id_explorador": "EX005",
"id_expedicion": "EP003",
"precio_pagado": 227.5
},
{
"id_explorador": "EX009",
"id_expedicion": "EP005",
"precio_pagado": 130.0
}
]

Rúbrica de evaluación​

Calificación total: 10 puntos​

ParteCriterioPuntuación
Parte 1Clase Explorador1
Parte 2Clase base Expedicion1,5
Parte 2Herencia y polimorfismo1,5
Parte 3 y 4Clase Agencia y lógica de negocio2
Parte 5Carga de datos CSV y JSON1,5
Parte 6Guardado de reservas en JSON0,75
Parte 7Métodos mágicos y representación textual0,5
GeneralCalidad, organización y encapsulación1,25

Desglose detallado​

1. Clase Explorador — 1 punto​

AspectoPuntos
Define correctamente los atributos indicados0,25
Valida correctamente el presupuesto0,25
Valida correctamente el nivel0,25
Implementa correctamente el método mágico __str__0,25

2. Clase base Expedicion — 1,5 puntos​

AspectoPuntos
Define correctamente el atributo compartido empresa0,2
Define correctamente los atributos de instancia0,3
Valida precio, duración, plazas y dificultad0,4
Calcula correctamente el precio final base0,25
Implementa correctamente hay_plazas_disponibles()0,15
Implementa correctamente reservar_plaza() y cancelar_plaza()0,2

3. Herencia y polimorfismo — 1,5 puntos​

AspectoPuntos
ExpedicionMontana hereda correctamente de Expedicion0,25
ExpedicionSelva hereda correctamente de Expedicion0,25
Se inicializan correctamente los atributos de la clase padre0,25
Se implementan los atributos específicos0,2
Se sobrescribe correctamente realizar_expedicion()0,25
Se sobrescribe correctamente calcular_precio_final() cuando corresponde0,3

4. Clase Agencia y lógica de negocio — 2 puntos​

AspectoPuntos
Gestiona correctamente exploradores, expediciones y reservas0,3
Implementa agregar_explorador() y agregar_expedicion()0,25
Implementa correctamente los métodos de búsqueda0,25
Realiza reservas comprobando existencia, plazas y presupuesto0,5
Cancela reservas devolviendo presupuesto y plaza0,3
Calcula correctamente la expedición más cara0,15
Devuelve correctamente expediciones disponibles0,1
Calcula correctamente la recaudación total0,1
Calcula correctamente el tipo más rentable0,1

5. Carga de datos CSV y JSON — 1,5 puntos​

AspectoPuntos
Carga correctamente exploradores.csv0,35
Ignora exploradores inválidos sin detener el programa0,25
Carga correctamente expediciones.csv0,35
Ignora expediciones inválidas sin detener el programa0,25
Carga correctamente reservas.json0,2
Ignora reservas inválidas sin detener el programa0,1

6. Guardado de reservas en JSON — 0,75 puntos​

AspectoPuntos
Crea correctamente el archivo JSON de salida0,25
Guarda todas las reservas actuales0,25
Respeta la estructura indicada0,25

7. Métodos mágicos — 0,5 puntos​

AspectoPuntos
__str__ de Explorador correcto0,2
__str__ de Expedicion correcto0,3

8. Calidad, organización y encapsulación — 1,25 puntos​

AspectoPuntos
No accede directamente a atributos desde fuera de las clases0,3
Código organizado por ficheros y responsabilidades0,25
Nombres claros y coherentes0,2
Uso adecuado de excepciones0,2
Código legible y sin duplicación excesiva0,2
Respeta nombres de clases, métodos y retornos solicitados0,1

Penalizaciones generales​

SituaciónPenalización
El programa no ejecuta por errores de sintaxisHasta -3
No se usa POOHasta -4
No se implementa herenciaHasta -1,5
No se implementa polimorfismoHasta -1
No se leen archivos externosHasta -2
No se gestiona JSONHasta -1
No se respeta la estructura del proyectoHasta -1
Se accede directamente a los atributos desde fuera de las clases de forma reiteradaHasta -1
El programa se detiene ante datos erróneos del datasetHasta -1
No se respetan los nombres pedidos de clases, métodos o retornosHasta -1,5

Observaciones​

  • No se deberá modificar el archivo main.py.
  • Se deberán respetar exactamente los nombres de clases, métodos y valores de retorno indicados.
  • Las filas incorrectas de los archivos de datos deberán ignorarse sin detener el programa.
  • Se permite el uso de excepciones para controlar errores.
  • Se valorará la claridad, la organización y la correcta separación de responsabilidades.
  • El acceso directo a atributos desde fuera de las clases no está permitido.
  • La solución debe poder ejecutarse cargando los datos desde los archivos proporcionados.